﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using ChartLib;
using System.Data;
using FdcDataInterface;
using System.Data.OracleClient;

namespace FdcChartData
{
    /// <summary>
    /// An implementation of IChartDataProvider for fdc data create chart.
    /// </summary>
    public class FdcChartProvider : IChartDataProvider
    {
        #region static Member
        public const string GroupByWafer = "Wafer";
        public const string GroupByLot = "Lot";
        #endregion

        #region Fields
        private MappedList<string, string> _stepToolHierarchy = null;
        private MappedList<string, string> _stepParameterHierarchy = null;
        
        private IDbConnection _dmsConnection = null;
        private IDbConnection _fdcConnection = null;

        private string _errorMsg ="";
        #endregion

        #region Properites
        
        public string ConnectString { get; set; }
        public IFdcInterface FdcInterface = null;
        public MappedList<string, string> StepToolHierarchy
        {
            get 
            {
                if (_stepToolHierarchy == null && DmsConnection.State == ConnectionState.Open)
                {
                    _stepToolHierarchy = new MappedList<string, string>();

                    IDbCommand cmd = DmsConnection.CreateCommand();
                    cmd.CommandText = @"select distinct step_id, tool_id from fdc_summary";
                    IDataReader reader = cmd.ExecuteReader();
                    while (reader.Read())
                    {
                        _stepToolHierarchy.Add(reader.IsDBNull(0) ? "UNKNOWN" : reader.GetString(0), reader.GetString(1));
                    }
                    reader.Close();
                }

                return _stepToolHierarchy;
            }
        }

        public MappedList<string, string> StepParameterHierarchy
        {
            get
            {
                if (_stepParameterHierarchy == null && DmsConnection.State == ConnectionState.Open)
                {
                    _stepParameterHierarchy = new MappedList<string, string>();

                    IDbCommand cmd = DmsConnection.CreateCommand();
                    cmd.CommandText = @"select distinct step_id, parameter, statistic from fdc_summary";
                    IDataReader reader = cmd.ExecuteReader();
                    while (reader.Read())
                    {
                        _stepParameterHierarchy.Add(reader.IsDBNull(0) ? "UNKNOWN" : reader.GetString(0), reader.GetString(1) + " " + reader.GetString(2));

                    }
                    reader.Close();
                }
                return _stepParameterHierarchy;
            }
        }

        public IDbConnection DmsConnection
        {
            get { return _dmsConnection; }
            set { _dmsConnection = value; }
        }

        /// <summary>
        /// Fdc connection info stored in dms_parameter table.
        /// </summary>
        public IDbConnection FdcConnection
        {
            get
            {
                try
                {
                    _errorMsg = "";

                    if (_fdcConnection == null)
                    {
                        if (DmsConnection != null)
                        {
                            if (DmsConnection.State != ConnectionState.Open)
                                DmsConnection.Open();

                            string userId = "";
                            string pswd = "";
                            string server = "";

                            IDbCommand cmd = DmsConnection.CreateCommand();
                            cmd.CommandText = @"select purpose, max(server) server,max(userid) userid ,max(pswd) pswd from(
                                select purpose,
                                case parameter_tag when 'DB_SERVER' then parameter_value else null end server, 
                                case parameter_tag when 'DB_USERID' then parameter_value else null end userid,
                                case parameter_tag when 'DB_PASSWORD' then parameter_value else null end pswd
                                from dms_parameter where purpose = 'FDC'
                                ) group by purpose";
                            IDataReader reader = cmd.ExecuteReader();
                            if (reader.Read())
                            {
                                server = reader.GetString(1);
                                userId = reader.GetString(2);
                                pswd = reader.GetString(3);
                            }
                            reader.Close();

                            if (server.Length > 0 && userId.Length > 0 && pswd.Length > 0)
                            {
                                string connectString = string.Format("Data Source={0};User ID={1};Password={2};", server, userId, pswd);
                                _fdcConnection = new OracleConnection(connectString);
                                _fdcConnection.Open();
                            }
                        }
                    }
                }
                catch (Exception ex)
                {
                    _fdcConnection = null;
                    _errorMsg = ex.Message;
                }
                return _fdcConnection;
            }
        }

        #endregion

        #region Constructor

        public FdcChartProvider(IDbConnection dbConn)
        {
            DmsConnection = dbConn;
            if (DmsConnection.State != ConnectionState.Open)
                DmsConnection.Open();
            if (FdcConnection != null)
            {
                if (FdcInterface == null)
                    FdcInterface = new FdcInterfaceArtistImpl();
                FdcInterface.DataConnection = FdcConnection;
            }
        }

        //public FdcChartProvider()
        //{
        //    DmsConnection = new OracleConnection();
        //    DmsConnection.ConnectionString = @"Data Source=10.1.4.178:1521/DMS;User ID=inspex;Password=inspex;";
        //    DmsConnection.Open();
        //    FdcInterface.DataConnection = FdcConnection;
        //}

        #endregion

        #region IChartDataProvider Members
        
        /// <summary>
        /// Get trend chart data from fdc summary table
        /// </summary>
        /// <param name="chartSetting"></param>
        /// <param name="conditions"></param>
        /// <returns></returns>
        public Dictionary<string, List<ScatterPlotData>> GetTrendData(ChartSettings chartSetting, IQueryCondition conditions)
        {
            //
            // fetch raw data
            //

            Dictionary<FdcSummaryDataItem, FdcSummaryDataAdapter> data = GetRawData(chartSetting, conditions);

            //
            //  Cover to chart data
            //
            Dictionary<string, List<ScatterPlotData>> returnData = new Dictionary<string, List<ScatterPlotData>>();
            int sequence = 0;
            foreach (KeyValuePair<string, List<string>> pair in chartSetting.Y1MultiTestId)
            {
                foreach (string testid in pair.Value)
                {
                    //Dictionary<string, ScatterPlotData> groupedData = new Dictionary<string, ScatterPlotData>();
                    //List<ScatterPlotData> parameterData = new List<ScatterPlotData>();
                    string comboTestId = pair.Key + " " + testid;

                    Dictionary<string, List<double>> X = new Dictionary<string, List<double>>();
                    Dictionary<string, List<double>> Y = new Dictionary<string, List<double>>();
                    Dictionary<string, List<string>> Labels = new Dictionary<string, List<string>>();
                    Dictionary<string, string> g3 = new Dictionary<string, string>();
                    Dictionary<string, string> g1 = new Dictionary<string, string>();
                    Dictionary<string, string> g2 = new Dictionary<string, string>();

                    Dictionary<string, List<double>> X2 = new Dictionary<string, List<double>>();
                    Dictionary<string, List<double>> Y2 = new Dictionary<string, List<double>>();
                    Dictionary<string, List<string>> Labels2 = new Dictionary<string, List<string>>();
                    int j = 0;

                    //returnData.Add(comboTestId, new List<ScatterPlotData>());
                    foreach (FdcSummaryDataAdapter w in data.Values)
                    {
                        if (!w.Match())
                            continue;

                        if (pair.Key != w.Item.StepId)
                            continue;

                        double y1 = w.GetY1TestValue(testid);
                        if (double.IsNaN(y1)) continue;
                        if (chartSetting.Y1Log && y1 <= 0.0) continue;

                        double x = w.XTestValue;

                        string key = w.Group1Value + ":" + w.Group2Value + ":" + w.Group3Value;

                        if (!X.ContainsKey(key))
                        {
                            X[key] = new List<double>();
                            Y[key] = new List<double>();
                            Labels[key] = new List<string>();
                            g3[key] = w.Group3Value;
                            g1[key] = w.Group1Value;
                            g2[key] = w.Group2Value;
                            X2[key] = new List<double>();
                            Y2[key] = new List<double>();
                            Labels2[key] = new List<string>();
                        }
                        X[key].Add(x);
                        Y[key].Add(y1);


                        if (chartSetting.Y2TestId != null)
                        {
                            double y2 = w.Y2TestValue;
                            if (double.IsNaN(y2)) continue;
                            if (chartSetting.Y2Log && y2 <= 0.0) continue;

                            Y2[key].Add(y2);
                            X2[key].Add(x);

                        }

                        // limit data count 
                        j++;
                        if (chartSetting.CountLimit > 0 && j >= chartSetting.CountLimit)
                            break;
                    }

                    ScatterPlotData[] rv = new ScatterPlotData[X.Count];
                    int i = 0;
                    foreach (string key in X.Keys)
                    {
                        rv[i] = new ScatterPlotData();
                        rv[i].count = X[key].Count;

                        SeriesStatistics stats = new SeriesStatistics();
                        stats.init(Y[key].ToArray());
                        rv[i].y1Stats = stats;

                        rv[i].xValue = X[key].ToArray();
                        rv[i].yValue = Y[key].ToArray();
                        rv[i].label = Labels[key].ToArray();
                        rv[i].g3 = g3[key];
                        rv[i].g1 = g1[key];
                        rv[i].g2 = g2[key];

                        if (chartSetting.Y2TestId != null)
                        {
                            stats = new SeriesStatistics();
                            stats.init(Y2[key].ToArray());
                            rv[i].y2Stats = stats;

                            rv[i].x2Value = X2[key].ToArray();
                            rv[i].y2Value = Y2[key].ToArray();
                            rv[i].label2 = Labels2[key].ToArray();
                        }
                        i++;
                    }
                    returnData[comboTestId] = rv.ToList();
                }
            }

            return returnData;
        }

        /// <summary>
        /// Get box plot chart data from fdc summary table
        /// </summary>
        /// <param name="chartSetting"></param>
        /// <param name="conditions"></param>
        /// <returns></returns>
        public List<BoxPlotData> GetBoxPlotData(ChartSettings chartSetting, IQueryCondition conditions)
        {

            //
            // fetch raw data
            //

            Dictionary<FdcSummaryDataItem, FdcSummaryDataAdapter> rawData = GetRawData(chartSetting, conditions);

            //
            //  Cover to chart data
            //

            Dictionary<string, List<double>> groupData = new Dictionary<string, List<double>>();
            Dictionary<string, DateTime> groupTime = new Dictionary<string, DateTime>();    //last time in group
            Dictionary<string, string[]> groupVals = new Dictionary<string, string[]>();

            foreach (FdcSummaryDataAdapter w in rawData.Values)
            {
                if (!w.Match())
                    continue;
                if (w.Item.StepId != chartSetting.Y1Step)
                    continue;

                string x1 = w.X1GroupValue;
                string x2 = w.X2GroupValue;
                string g3 = w.Group3Value;
                string g1 = w.Group1Value;
                string g2 = w.Group2Value;

                string group = g3 + ":" + x1 + ":" + x2 + ":" + g1 + ":" + g2;
                double y = w.Y1TestValue;
                if (double.IsNaN(y))
                    continue;

                if (!groupData.ContainsKey(group))
                {
                    groupData.Add(group, new List<double>());
                    groupTime.Add(group, DateTime.MinValue);
                    groupVals[group] = new string[] { x1, x2, g3, g1, g2 };
                }
                groupData[group].Add(y);
                if (w.XTimeValue > groupTime[group])
                    groupTime[group] = w.XTimeValue;
            }

            //_settings.XStep = null;

            BoxPlotData[] data = new BoxPlotData[groupData.Count];
            int n = 0;
            foreach (KeyValuePair<string, List<double>> pair in groupData)
            {
                pair.Value.Sort();
                data[n].xLabel = pair.Key;
                data[n].q1 = BoxPlotData.GetPercentile(pair.Value, 0.25);
                data[n].q2 = BoxPlotData.GetPercentile(pair.Value, 0.5);
                data[n].q3 = BoxPlotData.GetPercentile(pair.Value, 0.75);

                switch (chartSetting.BoxPlotStyle)
                {
                    case ChartSettings.BoxPlotType.FiveNinetyfive:
                        data[n].q0 = BoxPlotData.GetPercentile(pair.Value, 0.05);
                        data[n].q4 = BoxPlotData.GetPercentile(pair.Value, 0.95);
                        break;
                    case ChartSettings.BoxPlotType.IQR:
                        data[n].q0 = data[n].q2 - (data[n].q3 - data[n].q1) * 1.5;
                        data[n].q4 = data[n].q2 + (data[n].q3 - data[n].q1) * 1.5;
                        break;
                    case ChartSettings.BoxPlotType.MeanSigma:
                        double mu = BoxPlotData.GetAverage(pair.Value);
                        double sig = BoxPlotData.GetStdDev(pair.Value);
                        data[n].q0 = mu - 2 * sig;
                        data[n].q1 = mu - sig;
                        data[n].q3 = mu + sig;
                        data[n].q4 = mu + 2 * sig;
                        break;
                    case ChartSettings.BoxPlotType.MinMax:
                        data[n].q0 = pair.Value[0];
                        data[n].q4 = pair.Value[pair.Value.Count - 1];
                        break;
                    case ChartSettings.BoxPlotType.TenNinety:
                        data[n].q0 = BoxPlotData.GetPercentile(pair.Value, 0.10);
                        data[n].q4 = BoxPlotData.GetPercentile(pair.Value, 0.90);
                        break;
                    default:
                        // fall back to 5/95
                        data[n].q0 = BoxPlotData.GetPercentile(pair.Value, 0.05);
                        data[n].q4 = BoxPlotData.GetPercentile(pair.Value, 0.95);
                        break;
                }

                data[n].average = BoxPlotData.GetAverage(pair.Value);
                data[n].count = pair.Value.Count;
                data[n].lastTime = groupTime[pair.Key];
                data[n].stddev = BoxPlotData.GetStdDev(pair.Value);

                data[n].x1val = groupVals[pair.Key][0];
                data[n].x2val = groupVals[pair.Key][1];
                data[n].g3val = groupVals[pair.Key][2];
                data[n].g1val = groupVals[pair.Key][3];
                data[n].g2val = groupVals[pair.Key][4];

                data[n].min = pair.Value[0];
                data[n].max = pair.Value[pair.Value.Count - 1];

                data[n].outliers = new List<double>();
                foreach (double x in pair.Value)
                {
                    if (x < data[n].q0 || x > data[n].q4)
                        data[n].outliers.Add(x);
                }
                data[n].allValues = pair.Value;
                n++;
            }

            Array.Sort(data);
            for (int i = 0; i < data.Length; i++) data[i].index = i;

            return data.ToList();
        }

        /// <summary>
        /// get tool trace data from FDC server
        /// </summary>
        /// <param name="chartSetting"></param>
        /// <param name="lotWaferIds"></param>
        /// <returns> dictionary singal as ksy, tooltrackchartdata list as value</returns>
        public Dictionary<string, List<ToolTraceChartData>> GetToolTraceData(ToolTraceChartSettings chartSetting, Dictionary<string,List<string>> lotWaferIds)
        {
            Dictionary<string, List<ToolTraceChartData>> rv = new Dictionary<string,List<ToolTraceChartData>>();

            if (FdcInterface == null) return rv;

            // get history data
            var hist = FdcInterface.GetMoveInOutTimes(lotWaferIds);

            if (hist != null && hist.Count() > 0)
            {
                foreach (string signal in chartSetting.TraceSignal)
                {
                    if (!rv.ContainsKey(signal))
                        rv[signal] = new List<ToolTraceChartData>();
                    
                    ToolTraceChartData data = null;

                    foreach (LotWaferHistory w in hist)
                    {
                        if (data == null || Compare(data, w) != 0)
                        {

                            TraceDataSet ds = FdcInterface.GetTraceData(w.ToolId, signal, w.TimeIn, w.TimeOut);

                            if (ds != null && ds.Count > 0)
                            {
                                //
                                // create a new chart data item
                                //
                                data = new ToolTraceChartData();
                                data.EndTime = w.TimeOut;
                                data.StartTime = w.TimeIn;
                                data.ToolName = w.ToolId;
                                data.SignalName = signal;
                                
                                data.TrackableNames.Add(w.WaferId.Length >0? w.WaferId: w.LotId);

                                foreach (TraceDataItem v in ds)
                                {
                                    data.TraceX.Add(DateUtilities.EpochToDate((double)v.Epoch).ToOADate());
                                    data.TraceY.Add((double)v.Value);
                                }

                                // add it to the list
                                rv[signal].Add(data);
                            }

                        }
                        else
                        {
                            //
                            // This one is the same Tool/MoveIn/MovieOut as the last one, 
                            // just add the trackable and scalar value to it.
                            //
                            data.TrackableNames.Add(w.WaferId.Length > 0 ? w.WaferId : w.LotId);
                        }
                    }
                }

            }

            return rv;
        }

        /// <summary>
        /// Get correlation chart data from fdc summary table.
        /// </summary>
        /// <param name="chartSetting"></param>
        /// <param name="conditions"></param>
        /// <returns></returns>
        public List<ScatterPlotData> GetCorrelationData(ChartSettings chartSetting, IQueryCondition conditions)
        {
            chartSetting.Y1MultiTestId.Clear();
            chartSetting.Y1MultiTestId[chartSetting.Y1Step] = new List<string>();
            chartSetting.Y1MultiTestId[chartSetting.Y1Step].Add(chartSetting.Y1TestId);
            Dictionary<string,List<ScatterPlotData>> data =  GetTrendData(chartSetting, conditions);
            if (data.Count > 0)
                return data.Values.ElementAt(0);
            else
                return new List<ScatterPlotData>();
        }

        public SpecLimit GetSpecLimit(string stepId, string testId)
        {
            // cause of Fdc don't has context of limits, return a NaN value for chart library.
            SpecLimit l = new SpecLimit();
            l.StepName = stepId;
            l.MetricName = testId;
            l.SpecLimitHigh = double.NaN;
            l.SpecLimitLow = double.NaN;
            l.Target = double.NaN;
            return l;
        }

        public DataGroup[] GetDataForDataTable(ChartSettings chartSetting, IQueryCondition conditions)
        {

            //
            //  Get raw data
            //
            Dictionary<FdcSummaryDataItem, FdcSummaryDataAdapter> rawData = GetRawData(chartSetting, conditions);


            //
            // Generator data summary table
            //
            Dictionary<string, DataGroup> _data = new Dictionary<string, DataGroup>();



            if (chartSetting.TypeOfChart == ChartSettings.ChartType.Trend)
            {
                foreach (KeyValuePair<string, List<string>> pair in chartSetting.Y1MultiTestId)
                {
                    foreach (string testid in pair.Value)
                    {

                        string comboTestId = pair.Key + " " + testid;

                        foreach (FdcSummaryDataAdapter w in rawData.Values)
                        {
                            if (!w.Match())
                                continue;
                            if (w.Item.StepId != pair.Key)
                                continue;

                            string keybase = w.Group1Value + ":" + w.Group2Value + ":" + w.Group3Value
                                + ":" + w.X1GroupValue + ":" + w.X2GroupValue;

                            double val = w.GetY1TestValue(testid);
                            if (!double.IsNaN(val))
                            {
                                string key = keybase + ":" + comboTestId;

                                if (_data.ContainsKey(key) == false)
                                {
                                    _data[key] = CreateDataGroup(w, chartSetting.Y1Step, comboTestId);
                                }
                                _data[key].Add(val);
                            }
                            if (chartSetting.Y2TestId != null)
                            {
                                val = w.Y2TestValue;
                                if (!double.IsNaN(val))
                                {
                                    string key = keybase + ":" + chartSetting.Y2TestId;

                                    if (_data.ContainsKey(key) == false)
                                        _data[key] = CreateDataGroup(w, chartSetting.Y2Step, chartSetting.Y2TestId);
                                    _data[key].Add(val);
                                }
                            }
                            if (chartSetting.TypeOfChart == ChartSettings.ChartType.Correlation
                                || chartSetting.TypeOfChart == ChartSettings.ChartType.BubbleChart)
                            {
                                val = w.XTestValue;

                                if (!double.IsNaN(val))
                                {
                                    string key = keybase + ":" + chartSetting.XTestId;

                                    if (_data.ContainsKey(key) == false)
                                        _data[key] = CreateDataGroup(w, chartSetting.XStep, chartSetting.XTestId);

                                    _data[key].Add(val);
                                }
                            }
                        }
                    }
                }
            }
            else
            {
                foreach (FdcSummaryDataAdapter w in rawData.Values)
                {
                    if (!w.Match())
                        continue;
                    if (w.Item.StepId != chartSetting.Y1Step)
                        continue;

                    string keybase = w.Group1Value + ":" + w.Group2Value + ":" + w.Group3Value
                       + ":" + w.X1GroupValue + ":" + w.X2GroupValue;

                    double val = w.Y1TestValue;
                    if (!double.IsNaN(val))
                    {
                        string key = keybase + ":" + chartSetting.Y1TestId;

                        if (_data.ContainsKey(key) == false)
                        {
                            _data[key] = CreateDataGroup(w, chartSetting.Y1Step, chartSetting.Y1TestId);
                        }
                        _data[key].Add(val);
                    }
                    if (chartSetting.Y2TestId != null)
                    {
                        val = w.Y2TestValue;
                        if (!double.IsNaN(val))
                        {
                            string key = keybase + ":" + chartSetting.Y2TestId;

                            if (_data.ContainsKey(key) == false)
                                _data[key] = CreateDataGroup(w, chartSetting.Y2Step, chartSetting.Y2TestId);
                            _data[key].Add(val);
                        }
                    }
                    if (chartSetting.TypeOfChart == ChartSettings.ChartType.Correlation
                        || chartSetting.TypeOfChart == ChartSettings.ChartType.BubbleChart)
                    {
                        val = w.XTestValue;

                        if (!double.IsNaN(val))
                        {
                            string key = keybase + ":" + chartSetting.XTestId;

                            if (_data.ContainsKey(key) == false)
                                _data[key] = CreateDataGroup(w, chartSetting.XStep, chartSetting.XTestId);

                            _data[key].Add(val);
                        }
                    }
                }
            }


            if (_data.Count == 0) return new DataGroup[0];

            List<DataGroup> rv = new List<DataGroup>();
            foreach (DataGroup g in _data.Values)
            {
                g.vals.Sort();
                rv.Add(g);
            }
            rv.Sort();
            return rv.ToArray();
        }

        public List<ToolEvent> GetToolEvents(DateTime start, DateTime end, IEnumerable<string> eventTypes, IEnumerable<string> tools)
        {
            List<ToolEvent> data = new List<ToolEvent>();
            
            if (FdcInterface == null) return data;

            IEnumerable<EventOccurrence> eventList = FdcInterface.GetEventOccurrences(start, end, eventTypes, tools);
            foreach (EventOccurrence e in eventList)
            {
                ToolEvent te = new ToolEvent();
                te.Time = e.Time;
                te.EventName = e.EventType;
                te.ToolName = e.ToolId;
                data.Add(te);
            }

            return data;
        }

        /// <summary>
        /// This function return the choices for chart grouping
        /// </summary>
        /// <returns></returns>
        public TreeviewEntry[] GetGroupByChoices()
        {
            IDictionary<string, TreeviewEntry> tree = new Dictionary<string, TreeviewEntry>();

            tree["Tracking"] = new TreeviewEntry("Tracking");
            foreach (var t in new string[] { GroupByLot, GroupByWafer })
            {
                tree["Tracking"].AddChoice(t);
            }

            tree["Time"] = new TreeviewEntry("Time");
            tree["Time"].AddChoice(ChartSettings.Week);
            tree["Time"].AddChoice(ChartSettings.Day);
            tree["Time"].AddChoice(ChartSettings.Shift);
            tree["Time"].AddChoice(ChartSettings.Hour);

            tree["Tool"] = new TreeviewEntry("Tool");
            foreach (string stepid in StepToolHierarchy.Keys)
            {
                foreach (string toolid in StepToolHierarchy[stepid])
                {
                    tree["Tool"].AddValue(stepid, toolid);
                }
            }

            tree["Miscellaneous"] = new TreeviewEntry("Miscellaneous");
            tree["Miscellaneous"].AddValue(ChartSettings.DataGroup, "UNKNOWN");

            return tree.Values.ToArray();
        }

        /// <summary>
        /// This function return all the parameters
        /// </summary>
        /// <returns></returns>
        public TreeviewEntry[] GetScalarChoices() 
        {
            IDictionary<string, TreeviewEntry> tree = new SortedDictionary<string, TreeviewEntry>();

            foreach (string step in StepParameterHierarchy.Keys)
            {
                if (tree.ContainsKey(step) == false)
                {
                    tree[step] = new TreeviewEntry(step);
                }
                foreach(string parameter in StepParameterHierarchy[step])
                {
                    tree[step].AddChoice(parameter);
                }
            }

            return tree.Values.ToArray();
        }
        
        /// <summary>
        /// Time based parameter
        /// </summary>
        /// <returns></returns>
        public TreeviewEntry[] GetTimeChoices()
        {
            IDictionary<string, TreeviewEntry> tree = new SortedDictionary<string, TreeviewEntry>();
            foreach (string step in StepParameterHierarchy.Keys)
            {
                if (tree.ContainsKey(step) == false)
                {
                    tree[step] = new TreeviewEntry(step);
                }
                tree[step].AddChoice(ChartSettings.TestTime);
                tree[step].AddChoice(ChartSettings.TimeSequence);
            }

            return tree.Values.ToArray();
        }

        /// <summary>
        /// Get trace singal choices
        /// </summary>
        /// <returns></returns>
        public TreeviewEntry[] GetTraceSignalChoices(List<string> toolList)
        {
            if (FdcInterface == null) return new TreeviewEntry[0];

            MappedList<string, string> signals = FdcInterface.GetTraceSignals(toolList);
            List<TreeviewEntry> result = new List<TreeviewEntry>();
            
            if (signals != null)
            {
                foreach (string key in signals.Keys)
                {
                    TreeviewEntry entry = new TreeviewEntry();
                    entry.Category = key;
                    List<string> sortList = signals[key].ToList();
                    sortList.Sort();
                    foreach (string ch in sortList)
                        entry.AddChoice(ch);
                    result.Add(entry);
                }
            }
            return result.ToArray();
        }

        //
        // Data Grouping Functions
        //

        public DataGroupingContext CreateDataGroupFromFilter(FilterImpl filters, string groupName, DataGroupingContext context) 
        {
            return new DataGroupingContext(groupName);
        }

        public DataGroupingContext RemoveDataGroup(string groupName, DataGroupingContext context)
        {
            return new DataGroupingContext(groupName);
        }

        public DataGroupingContext RenameDataGroup(string groupName, string newName, DataGroupingContext context)
        {
            return new DataGroupingContext(newName);
        }

        public bool ClearDataGroups(DataGroupingContext context)
        {
            return true;
        }


        /// <summary>
        /// help function to detect if data source is available
        /// </summary>
        /// <param name="error"></param>
        /// <returns></returns>
        public bool IsDataSourceAvailable(out string error)
        {
            error = _errorMsg;
            return (_fdcConnection != null);
        }

        #endregion

        #region Private Members
        private void SummarizeScatterData(List<double> x, List<double> y, List<string> group)
        {
            Dictionary<string, double> sumx = new Dictionary<string, double>();
            Dictionary<string, double> sumy = new Dictionary<string, double>();
            Dictionary<string, int> count = new Dictionary<string, int>();

            int n = x.Count;
            for (int i = 0; i < n; i++)
            {
                string key = group[i];

                if (!sumx.ContainsKey(key))
                {
                    sumx[key] = x[i];
                    sumy[key] = y[i];
                    count[key] = 1;
                }
                else
                {
                    sumx[key] += x[i];
                    sumy[key] += y[i];
                    count[key]++;
                }
            }
            x.Clear(); y.Clear(); group.Clear();
            foreach (string key in sumx.Keys)
            {
                x.Add(sumx[key] / count[key]);
                y.Add(sumy[key] / count[key]);
                group.Add(key);
            }
        }
        private void SummarizeTimeSeries(List<double> x, List<double> y, List<string> group)
        {
            Dictionary<string, double> minx = new Dictionary<string, double>();
            Dictionary<string, double> sumy = new Dictionary<string, double>();
            Dictionary<string, int> count = new Dictionary<string, int>();

            int n = x.Count;
            for (int i = 0; i < n; i++)
            {
                string key = group[i];

                if (!minx.ContainsKey(key))
                {
                    minx[key] = x[i];
                    sumy[key] = y[i];
                    count[key] = 1;
                }
                else
                {
                    minx[key] = Math.Min(minx[key], x[i]);
                    sumy[key] += y[i];
                    count[key]++;
                }
            }
            x.Clear(); y.Clear(); group.Clear();
            foreach (string key in minx.Keys)
            {
                x.Add(minx[key]);
                y.Add(sumy[key] / count[key]);
                group.Add(key);
            }
        }
        
        /// <summary>
        /// Used to create a data group for returning the data for the summary
        /// table below the chart.
        /// </summary>
        /// <param name="w"></param>
        /// <param name="step"></param>
        /// <param name="testid"></param>
        /// <returns></returns>
        private DataGroup CreateDataGroup(FdcSummaryDataAdapter w, string step, string testid)
        {
            DataGroup g = new DataGroup();
            g.Group1 = w.Group1Value;
            g.Group2 = w.Group2Value;
            g.Group3 = w.Group3Value;
            g.X1Group = w.X1GroupValue;
            g.X2Group = w.X2GroupValue;
            if (step != null) g.Step = step;
            if (testid != null) g.TestId = testid;
            return g;
        }

        /// <summary>
        /// Get raw data and save to a dictionary.
        /// </summary>
        /// <param name="settings"></param>
        /// <param name="conditions"></param>
        /// <returns></returns>
        private Dictionary<FdcSummaryDataItem, FdcSummaryDataAdapter> GetRawData(ChartSettings settings, IQueryCondition conditions)
        {
            //
            // fetch raw data
            //

            Dictionary<FdcSummaryDataItem, FdcSummaryDataAdapter> data = new Dictionary<FdcSummaryDataItem, FdcSummaryDataAdapter>(new FDCItemEqualityComparer());

            List<ScatterPlotData> spData = new List<ScatterPlotData>();
            string whereString = "";
            string cmdString = "select LOT_ID, WAFER_ID, STEP_ID, TOOL_ID, DATE_TIME, PARAMETER, STATISTIC, VALUE from FDC_SUMMARY";

            // no condition, retrieval all datas
            if (conditions != null)
            {
                whereString += conditions.WhereSql();
            }

            if (whereString.Length > 0)
                cmdString += " where " + whereString;
            
            // make it time based
            cmdString += " order by DATE_TIME";
            
            IDbCommand cmd = DmsConnection.CreateCommand();
            cmd.CommandText = cmdString;
            IDataReader reader = cmd.ExecuteReader();

            //int sequence = 0;

            while (reader.Read())
            {
                //TrackableDataSet item = new TrackableDataSet(reader.GetString(0), reader.IsDBNull(1)?"":reader.GetString(1), reader.GetDateTime(4));
                FdcSummaryDataItem item = new FdcSummaryDataItem(reader.GetString(0), reader.IsDBNull(1) ? "" : reader.GetString(1), reader.IsDBNull(2) ? "UNKNOWN" : reader.GetString(2), reader.GetString(3), reader.GetDateTime(4));
                
                if (!data.ContainsKey(item))
                {
                    data[item] = new FdcSummaryDataAdapter(settings);
                    data[item].Item = item;
                    //data[item].Sequence = sequence;
                    //sequence++;
                }
                data[item].Item.Add(reader.GetString(5), reader.GetString(6), reader.GetDouble(7));
                //data[item].Item.Add(reader.IsDBNull(2) ? "UNKNOWN" : reader.GetString(2), reader.GetString(3), reader.GetString(5), reader.GetString(6), reader.GetDouble(7));
            }
            reader.Close();

            return data;
        }

        /// <summary>
        /// compare tool trace data and wafer history
        /// </summary>
        /// <param name="chartData"></param>
        /// <param name="hist"></param>
        /// <returns></returns>
        private int Compare(ToolTraceChartData chartData, LotWaferHistory hist)
        {
            int rv = chartData.ToolName.CompareTo(hist.ToolId);
            if (rv == 0)
                rv = chartData.StartTime.CompareTo(hist.TimeIn);
            if (rv == 0)
                rv = chartData.EndTime.CompareTo(hist.TimeOut);
            return rv;
        }

        #endregion

    }

    /// <summary>
    /// this is a class like waferadapter in discover solar.
    /// all the get data function for chart can implement based on this data structor
    /// </summary>
    public class FdcSummaryDataAdapter : IChartDataAdapter
    {
        public FdcSummaryDataItem Item { get; set; } 
        public int Sequence { get; set; }
        public ChartSettings Settings { get; set; }
        
        public FdcSummaryDataAdapter(ChartSettings setting)
        {
            Settings = setting;
        }

        internal double getTestValue(string parameter, string statistic)
        {
            if (parameter.Length > 0)
            {
                return Item.GetValue(parameter, statistic);
            }
            else
                return double.NaN;
        }

        /// <summary>
        /// Return a group value based upon the group type given in the
        /// argument. This argument should be one of the string constants
        /// defined in ChartSettings
        /// </summary>
        /// <param name="group"></param>
        /// <returns></returns>
        internal string getGroupValue(string group)
        {
            if (string.IsNullOrEmpty(group))
                return EmptyGroupValue;

            
            //string step = (Settings.XStep == null ? Settings.Y1Step : Settings.XStep);
            //string metric = Settings.Y1TestId;
            switch (group)
            {
                // for timing group
                case ChartSettings.Hour:
                    {
                        return Item.Time.ToString("yyyy-MM-dd HH");
                    }
                case ChartSettings.Day:
                    {
                        return Item.Time.ToString("yyyy-MM-dd");
                    }
                case ChartSettings.Week:
                    {
                        return string.Format("{0} W{1:D2}", Item.Time.Year,
                            System.Globalization.CultureInfo.CurrentCulture.Calendar.GetWeekOfYear(
                                Item.Time,
                                System.Globalization.CalendarWeekRule.FirstDay,
                                DayOfWeek.Sunday));

                    }
                case ChartSettings.Shift:
                    {
                        return Item.Time.ToString(System.Globalization.CultureInfo.CurrentCulture.DateTimeFormat.ShortDatePattern)
                                + " " + WorkShiftUtil.GetShiftName(Item.Time.TimeOfDay);
                    }

                // for tracking group    
                case FdcChartProvider.GroupByWafer:
                    {
                        return Item.WaferId;
                    }
                case FdcChartProvider.GroupByLot:
                    {
                        return Item.LotId;
                    }

                default:
                    {
                        // for step/tool group
                        if (group == Item.StepId)
                            return Item.ToolId;
                        else
                            return EmptyGroupValue;

                        //FdcSummaryDataItem item = Item.Items.Find(i => i.StepId == group);
                        //if (item!= null)
                        //    return item.ToolId;
                        //else
                        //    return EmptyGroupValue;

                        // [Warning] need implement when we get Data Grouping Manager
                        //string v = dataset.getGroupValue(trackableId, group, step, metric,
                        //    settings.DataGroupingContext);
                        //if (group == ChartSettings.DataGroup && v == "NONE")
                        //{
                        //    var e = DataGroupingManager.GetSummaryTableDataGroupingEntry(settings.DataGroupingContext);
                        //    if (e != null)
                        //    {
                        //        foreach (string g in e.GetGroups())
                        //        {
                        //            var f = e.GetFilters(g);
                        //            if (f.Match(this)) return g;
                        //        }
                        //    }
                        //}
                        
                    }
            }
        }


        #region IChartDataAdapter Members

        public DateTime XTimeValue
        {
            get
            {
                return Item.Time;
            }
        }

        public int XTimeSequence
        {
            get { return Sequence; }
        }

        public double XTestValue
        {
            get
            {
                if (Settings.TypeOfChart == ChartSettings.ChartType.Trend)
                {
                    if (Settings.XTestId == ChartSettings.TimeSequence)
                        return Sequence;
                    else
                        return XTimeValue.ToOADate();
                }
                string[] param = Settings.XTestId.Split(new char[1] { ' ' });
                if (param.Length == 2)
                    return Item.GetValue(param[0],param[1]);
                
                return double.NaN;
            }
        }
        
        public double GetY1TestValue(string testid)
        {
            string[] param = testid.Split(new char[1] { ' ' });
            if (param.Length == 2)
                return Item.GetValue(param[0], param[1]);

            return double.NaN;
        }

        public double Y1TestValue
        {
            get
            {
                string[] param = Settings.Y1TestId.Split(new char[1] { ' ' });
                if (param.Length == 2)
                    return Item.GetValue(param[0], param[1]);

                return double.NaN;
            }
        }

        public double Y2TestValue
        {
            get
            {
                string[] param = Settings.Y2TestId.Split(new char[1] { ' ' });
                if (param.Length == 2)
                    return Item.GetValue(param[0], param[1]);

                return double.NaN;
            }
        }

        public string EmptyGroupValue
        {
            get { return "All"; }
        }

        public string X1GroupValue
        {
            get { return getGroupValue(Settings.X1Group); }
        }

        public string X2GroupValue
        {
            get { return getGroupValue(Settings.X2Group); }
        }

        public string Group1Value
        {
            get { return getGroupValue(Settings.Group1); }
        }

        public string Group2Value
        {
            get { return getGroupValue(Settings.Group2); }
        }

        public string Group3Value
        {
            get { return getGroupValue(Settings.Group3); }
        }

        public string DataSummaryValue
        {
            get { throw new NotImplementedException(); }
        }

        #endregion

        #region Filter Data
        public bool Match()
        {
            FilterImpl filters  = Settings.Filters;
            
            if (filters == null) return true;

            foreach (FilterCondition c in filters.Conditions)
            {
                IComparable val = null;
                if (c is GroupByFilterCondition)
                {
                    val = getGroupValue(c.FieldName);
                }
                else if (c is ToolTypeFilterCondition)
                {
                    // fdc don't have tool type
                    val = "UNKNOWN";
                }
                else if (c is ScalarFilterCondition)
                {
                    FdcSummaryDataItem item = Item;
                    if (item != null)
                    {
                        // for fdc the parameter is signal+{space}+statistic
                        string[] param = (c as ScalarFilterCondition).MetricName.Split(' ');
                        if(param.Length == 2)
                        {
                            val = item.GetValue(param[0], param[1]);
                        } 
                    }
                }
                else if (c is ProcessTimeFilterCondition)
                {
                    val = Item.Time;
                }
                else if (c is ProcessSequenceFilterCondition)
                {
                    val = Sequence; 
                    // this is not the true sequence, to have a time based sequence, we need add a order by when query data
                }
                else if (c is TrackableIdFilterCondition)
                {
                    val = Item.WaferId; 
                    // [Warning] lotid or waferid ?
                }
                else if (c is UserSelectionFilterCondition)
                {
                    // only for spc chart data, not support for now
                    //foreach (IComparable v in c.Values)
                    //    if (ds.InSelection(v.ToString(), trackableId)) continue;
                    return false;
                }
                else if (c is PolygonFilterCondition)
                {
                    PolygonFilterCondition f = c as PolygonFilterCondition;
                    
                    // get y value
                    double y = double.NaN;

                    // if parameter is formatted as singal+{space}+statistic
                    // if parameter is not from fdc sumamry, get the y value based on parameter name.
                    if (f.YTestid.Contains(' '))
                    {
                        string[] param = f.YTestid.Split(' ');
                        if (param.Length == 2)
                        {
                            y = Item.GetValue(param[0], param[1]);
                        }
                        if (double.IsNaN(y)) return false;
                    }

                    // get x value
                    double x = double.NaN;
                    if (f.XTestid == ChartSettings.TestTime)
                    {
                        DateTime t = Item.Time;
                        x = t.ToOADate();
                    }
                    else if (f.XTestid == ChartSettings.TimeSequence)
                    {
                        x = Sequence;
                    }
                    else
                    {
                        if(f.XTestid.Contains(' '))
                        {
                            string[] param = f.XTestid.Split(' ');
                            if (param.Length == 2)
                            {
                                x = Item.GetValue(param[0], param[1]);
                            }
                        }
                    }
                    if (double.IsNaN(x)) return false;


                    
                    PointD point = new PointD(x, y);
                    
                    if (!f.isMatch(point)) 
                        return false;
                    else  
                        continue;
                }
                else throw new Exception("Unknown filter condition type");

                if (!c.isMatch(val)) return false;
            }

            return true;
        }

        #endregion
    }

    /// <summary>
    ///  FdcSummaryDataItem can not support multi parameter as well,
    ///  use TrackableDataSet instead of.
    /// </summary>
    public class TrackableDataSet : IComparable<TrackableDataSet>
    {
        private List<FdcSummaryDataItem> _items = new List<FdcSummaryDataItem>();
        public string LotId { get; set; }
        public string WaferId { get; set; }
        public DateTime Time { get; set; }

        public List<FdcSummaryDataItem> Items
        {
            get {return _items;}
            set {_items = value;}
        }

        public TrackableDataSet(string lot, string wafer, DateTime dt)
        {
            LotId = lot;
            WaferId = wafer;
            Time = dt;
        }

        public void Add(string step, string tool, string parameter, string statistic, double value)
        {
            //if (Items.Any(i => i.StepId == step && i.ToolId == tool))
            //{
            FdcSummaryDataItem item = _items.Find(i => i.StepId == step && i.ToolId == tool);
            if (item == null)
            {
                item = new FdcSummaryDataItem(LotId, WaferId, step, tool, Time);
                _items.Add(item);
            }
            item.Add(parameter, statistic, value);
        }

        public double GetValue(string step, string tool, string parameter, string statistic)
        {
            FdcSummaryDataItem item = _items.Find(i => i.StepId == step && i.ToolId == tool);
            if (item != null)
                return item.GetValue(parameter, statistic);
            else
                return double.NaN;
        }

        /// <summary>
        ///  find any matched parameter and statistic and return it.
        ///  better to not use above one instead of.
        /// </summary>
        /// <param name="parameter"></param>
        /// <param name="statistic"></param>
        /// <returns></returns>
        public double GetValue(string parameter, string statistic)
        {
            foreach (FdcSummaryDataItem item in _items)
            {
                double value = item.GetValue(parameter, statistic);
                if (!double.IsNaN(value))
                    return value;
                else
                    continue;
            }
            return double.NaN;
        }


        #region IComparable<TrackableDataSet> Members

        public int CompareTo(TrackableDataSet other)
        {
            int rev = LotId.CompareTo(other.LotId);
            if (rev == 0)
            {
                return WaferId.CompareTo(other.WaferId);
            }
            return rev;
        }

        #endregion

    }


    /// <summary>
    /// to make TrackableDataSet as a key of dictionary
    /// </summary>
    public class TDSEqualityComparer : IEqualityComparer<TrackableDataSet>
    {

        #region IEqualityComparer<TrackableDataSet> Members

        public bool Equals(TrackableDataSet x, TrackableDataSet y)
        {
            return (x.LotId == y.LotId && x.WaferId == y.WaferId);
        }

        public int GetHashCode(TrackableDataSet obj)
        {
            return  GetHashCode();
        }

        #endregion
    }
    /// <summary>
    /// to make TrackableDataSet as a key of dictionary
    /// </summary>
    public class FDCItemEqualityComparer : IEqualityComparer<FdcSummaryDataItem>
    {

        #region IEqualityComparer<FdcSummaryDataItem> Members

        public bool Equals(FdcSummaryDataItem x, FdcSummaryDataItem y)
        {
            return (x.LotId == y.LotId && x.WaferId == y.WaferId);
        }

        public int GetHashCode(FdcSummaryDataItem obj)
        {
            return GetHashCode();
        }

        #endregion
    }

    /// <summary>
    /// Used for converting a Time into a work shift.
    /// </summary>
    public class WorkShiftUtil
    {
        private static List<KeyValuePair<TimeSpan, string>> m_workShifts;

        static WorkShiftUtil()
        {
            //we don't have workshift definitions, only support default value.
            string[] shifts =  ("00:00-Shift1,08:00-Shift2,16:00-Shift3").Split(',');

            m_workShifts = new List<KeyValuePair<TimeSpan, string>>();
            foreach (string s in shifts)
            {
                int n = s.IndexOf('-');
                if (n > 0)
                {
                    string startingTime = s.Substring(0, n);
                    string shiftName = s.Substring(n + 1);
                    try
                    {
                        m_workShifts.Add(new KeyValuePair<TimeSpan, string>(TimeSpan.Parse(startingTime), shiftName));
                    }
                    catch (Exception) { }
                }
            }
            m_workShifts.Sort(delegate(KeyValuePair<TimeSpan, string> v1, KeyValuePair<TimeSpan, string> v2)
            {
                return v1.Key.CompareTo(v2.Key);
            });
        }

        public static string GetShiftName(TimeSpan timeOfDay)
        {
            for (int i = 0; i < m_workShifts.Count; i++)
                if (timeOfDay < m_workShifts[i].Key)
                    return i == 0 ? m_workShifts[m_workShifts.Count - 1].Value : m_workShifts[i - 1].Value;
            return m_workShifts.Count == 0 ? string.Empty : m_workShifts[m_workShifts.Count - 1].Value;
        }
    }

}
